Back to Article
Sizing for a 2 Stage OTA
Download Notebook
In [1]:
# Sizing for a 2 Stage OTA
from pygmid import Lookup as lk
import numpy as np
lv_nmos = lk('sg13_lv_nmos.mat')
lv_pmos = lk('sg13_lv_pmos.mat')
# list of parameters: VGS, VDS, VSB, L, W, NFING, ID, VT, GM, GMB, GDS, CGG, CGB, CGD, CGS, CDD, CSS, STH, SFL
# if not specified, minimum L, VDS=max(vgs)/2=0.9 and VSB=0 are used 
In [2]:
# define the given parameters as taken from the specification table or inital guesses
c_load = 1e-12
#c_load = 1e-12
gm_id_m12 = 11
gm_id_m34 = 7
gm_id_m5 = 10
gm_id_m678 = 6
l_12 = 2
l_34 = 2
l_5 = 2
l_678 = 2
f_bw = 10e6 # -3dB bandwidth of the voltage buffer
i_total_limit = 30e-6
i_bias_in = 20e-6
output_voltage = 1.3
vin_min = 0.7
vin_max = 0.9
vdd_min = 1.45
vdd_max = 1.55
gain = 60
In [3]:
c_m_min = 0.22*c_load
# Industrial scaling multiplier (6.6x the lower bound) to swallow up local device parasitics
c_m = 0.5*c_load
#c_m = 150e-16
#c_m = c_m_min
In [4]:
print('\n--- Step 2: First Stage Core Sizing ---')
gm_m12_ideal = 3*(2 * np.pi * f_bw) * c_m
id_m12_ideal = gm_m12_ideal / gm_id_m12
i_tail_ideal = 2 * id_m12_ideal

# Snapping tail bias current parameter smoothly onto physical layout grid (0.5 µA blocks)
i_tail = max(round(i_tail_ideal / 1e-6 * 2) / 2 * 1e-6, 0.5e-6)
id_m12 = i_tail / 2   # Re-establish physical branch balance post-quantization
gm_m12 = gm_id_m12 * id_m12  # Re-evaluate true manufactured transconductance from grid current

print(f'Required Analytical gm12 = {gm_m12_ideal*1e3:.5f} mS')
print(f'Grid Re-aligned I_tail   = {i_tail/1e-6:.2f} µA')
print(f'True Manufactured gm12   = {gm_m12*1e3:.5f} mS')

--- Step 2: First Stage Core Sizing ---
Required Analytical gm12 = 0.09425 mS
Grid Re-aligned I_tail   = 17.00 µA
True Manufactured gm12   = 0.09350 mS
In [5]:
print('\n--- Step 3: Second Stage Stability-Driven Constraints ---')
# Image Sizing Rule:
gm_m5_req = 2.2*f_bw*2*np.pi*c_load
print(f'Required Minimum Stage 2 transconductance gm5 = {gm_m5_req*1e3:.5f} mS')

id_m5_required = gm_m5_req / gm_id_m5
print(f'Analytically Required Stage 2 Bias Current    = {id_m5_required/1e-6:.3f} µA')

--- Step 3: Second Stage Stability-Driven Constraints ---
Required Minimum Stage 2 transconductance gm5 = 0.13823 mS
Analytically Required Stage 2 Bias Current    = 13.823 µA
In [6]:
# we calculate the first stage  dc gain
gm_gds_m12 = lv_nmos.lookup('GM_GDS', GM_ID=gm_id_m12, L=l_12, VDS=0.75, VSB=0)
gm_gds_m34 = lv_pmos.lookup('GM_GDS', GM_ID=gm_id_m34, L=l_34, VDS=0.75, VSB=0)

gds_m12 = gm_m12 / gm_gds_m12
gm_m34 = gm_id_m34 * i_tail/2
gds_m34 = gm_m34 / gm_gds_m34

a0 = gm_m12 / (gds_m12 + gds_m34)
print('First stage gain a0 =', round(20*np.log10(a0), 1), 'dB')
First stage gain a0 = 31.6 dB
In [7]:
# we can now look up the VGS of the MOSFET
vgs_m12 = lv_nmos.look_upVGS(GM_ID=gm_id_m12, L=l_12, VDS=0.75, VSB=0.0)
vgs_m34 = lv_pmos.look_upVGS(GM_ID=gm_id_m34, L=l_34, VDS=0.75, VSB=0.0) 
vgs_m76 = lv_nmos.look_upVGS(GM_ID=gm_id_m678, L=l_678, VDS=0.75, VSB=0.0) 

print('vgs_12 =', round(float(vgs_m12), 3), 'V')
print('vgs_34 =', round(float(vgs_m34), 3), 'V')
print('vgs_76 =', round(float(vgs_m76), 3), 'V')
vgs_12 = 0.37 V
vgs_34 = 0.617 V
vgs_76 = 0.547 V
In [8]:
# calculate all widths
id_w_m12 = lv_nmos.lookup('ID_W', GM_ID=gm_id_m12, L=l_12, VDS=vgs_m12, VSB=0)
w_12 = id_m12 / id_w_m12
w_12_round = max(round(w_12*2)/2, 0.5)
print('M1/2 W =', round(w_12, 2), 'um, rounded W =', w_12_round, 'um')

id_m34 = id_m12
id_w_m34 = lv_pmos.lookup('ID_W', GM_ID=gm_id_m34, L=l_34, VDS=vgs_m34, VSB=0)
w_34 = id_m34 / id_w_m34
w_34_round = max(round(w_34*2)/2, 0.5) 
print('M3/4 W =', round(w_34, 2), 'um, rounded W =', w_34_round, 'um')

id_w_m7 = lv_nmos.lookup('ID_W', GM_ID=gm_id_m678, L=l_678, VDS=vgs_m76, VSB=0)
w_7 = i_tail / id_w_m7
w_7_round = max(round(w_7*2)/2, 0.5)
print('M7 W =', round(w_7, 2), 'um, rounded W =', w_7_round, 'um')

w_6 = w_7_round * i_bias_in / i_tail
w_6_round = max(round(w_6*2)/2, 0.5)
print('M6 W =', round(w_6_round, 2), 'um')

# ==============================================================================
# Sizing for Second Stage: M8 and M5
# ==============================================================================
print('\n--- Second Stage Sizing ---')

# Step A: Size M8 (Stage 2 NMOS Load Mirror) using id_m5_required
id_w_m8 = lv_nmos.lookup('ID_W', GM_ID=gm_id_m678, L=l_678, VDS=0.75, VSB=0)
w_8 = id_m5_required / id_w_m8
w_8_round = max(round(w_8 * 2) / 2, 0.5)
print('M8 W =', round(w_8, 2), 'um, rounded W =', w_8_round, 'um')

# Step B: Realign true mirrored current to layout grid constraints
mirror_ratio_m8_m6 = w_8_round / w_6_round
id_m5 = i_bias_in * mirror_ratio_m8_m6
print(f'True Mirrored Stage 2 Current (id_m5) = {id_m5/1e-6:.3f} µA')

# Step C: Re-calculate actual physical gm5 and size M5 (PMOS Driver)
gm_m5 = gm_id_m5 * id_m5
id_w_m5 = lv_pmos.lookup('ID_W', GM_ID=gm_id_m5, L=l_5, VDS=0.75, VSB=0)
w_5 = id_m5 / id_w_m5
w_5_round = max(round(w_5 * 2) / 2, 0.5)
print('M5 W =', round(w_5, 2), 'um, rounded W =', w_5_round, 'um')

# Step D: Complete the flow with your Zero-Nulling Resistor value
rz = 2 / gm_m5
print(f'Textbook Zero-Nulling Resistor Rz = {rz/1e3:.2f} kΩ')
M1/2 W = 2.7 um, rounded W = 2.5 um
M3/4 W = 3.55 um, rounded W = 3.5 um
M7 W = 1.25 um, rounded W = 1.5 um
M6 W = 2.0 um

--- Second Stage Sizing ---
M8 W = 1.01 um, rounded W = 1.0 um
True Mirrored Stage 2 Current (id_m5) = 10.000 µA
M5 W = 9.27 um, rounded W = 9.5 um
Textbook Zero-Nulling Resistor Rz = 20.00 kΩ
In [9]:
# ==============================================================================
# 5. Total Open-Loop DC Gain Evaluation
# ==============================================================================
print('\n--- Total DC Gain Evaluation ---')

# Second stage gain evaluation
# Extract the intrinsic gain of the PMOS Common-Source driver (M5)
gm_gds_m5 = lv_pmos.lookup('GM_GDS', GM_ID=gm_id_m5, L=l_5, VDS=0.75, VSB=0)
gds_m5    = gm_m5 / gm_gds_m5

# Extract the output conductance density of the NMOS mirror load (M8)
gds_id_m8 = lv_nmos.lookup('GDS_ID', GM_ID=gm_id_m678, L=l_678, VDS=0.75, VSB=0)
gds_m8    = gds_id_m8 * id_m5

# Compute Stage 2 Gain
a1 = gm_m5 / (gds_m5 + gds_m8)

# Compute Total Open-Loop Gain
a0_total = a0 * a1

print(f'Stage 1 Open-Loop Gain (A1) = {20*np.log10(a0):.1f} dB')
print(f'Stage 2 Open-Loop Gain (A2) = {20*np.log10(a1):.1f} dB')
print(f'Total Open-Loop Gain (A0)   = {20*np.log10(a0_total):.1f} dB')

gain_error = a0_total / (1 + a0_total)
print('voltage gain error =', round((gain_error-1)*100, 1), '%')

--- Total DC Gain Evaluation ---
Stage 1 Open-Loop Gain (A1) = 31.6 dB
Stage 2 Open-Loop Gain (A2) = 34.3 dB
Total Open-Loop Gain (A0)   = 65.9 dB
voltage gain error = -0.1 %
In [10]:
# ==============================================================================
# 6. Node Parasitic Extraction, Actual UGBW, and Final Summary
# ==============================================================================
print('--- Node Parasitic Extraction & Actual UGBW ---')

# Look up frequency-dependent parasitic caps
gm_cgs_m12  = lv_nmos.lookup('GM_CGS', GM_ID=gm_id_m12, L=l_12,  VDS=0.75, VSB=0)
gm_cdd_m12  = lv_nmos.lookup('GM_CDD', GM_ID=gm_id_m12, L=l_12,  VDS=0.75, VSB=0)
gm_cdd_m34  = lv_pmos.lookup('GM_CDD', GM_ID=gm_id_m34, L=l_34,  VDS=0.75, VSB=0)

# NEW: Look up the gate-capacitance of the M5 driver
gm_cgg_m5   = lv_pmos.lookup('GM_CGG', GM_ID=gm_id_m5,   L=l_5,   VDS=0.75, VSB=0)

gm_w_m34 = lv_pmos.lookup('GM_W', GM_ID=gm_id_m34, L=l_34, VDS=0.75, VSB=0)
gm_m34   = gm_w_m34 * w_34_round

# NEW: Add the M5 gate load to the total internal node parasitic calculation
c_para_internal = abs(gm_m12 / gm_cgs_m12) + abs(gm_m12 / gm_cdd_m12) + abs(gm_m34 / gm_cdd_m34) + abs(gm_m5 / gm_cgg_m5)

# The effective Miller capacitance includes this parallel parasitic loading
c_m_eff = c_m + c_para_internal

# Calculate the actual physical UGBW roll-off from the true c_m_eff
f_bw_actual = gm_m12 / (2 * np.pi * c_m_eff)

t_slew = c_m_eff*output_voltage / i_tail
print('slewing time =', round(t_slew/1e-6, 3), 'µs')

tau = 1 / (2 * np.pi * f_bw_actual)
t_settle = 5 * tau
print('settling time =', round(t_settle/1e-6, 3), 'µs')

print(f'Internal Node Parasitic Load = {c_para_internal/1e-15:.2f} fF and of which m5 {abs(gm_m5 / gm_cgg_m5)/1e-15}')
print(f'Effective Miller Capacitance = {c_m_eff/1e-15:.2f} fF')

i_total_rail = i_bias_in + i_tail + id_m5

# Power calculation at the minimum and maximum VDD limits
power_dissipation_min = i_total_rail * vdd_min
power_dissipation_max = i_total_rail * vdd_max
print(f"Total Current from VDD Rail = {i_total_rail/1e-6:.2f} µA")

# ==============================================================================
# 7. Final Industrial Sizing Blueprint Summary Printout
# ==============================================================================
cap_density = 1.5e-15
w_cm_approx = np.sqrt(c_m / cap_density)

print('\n============================================================')
print('  Textbook-Aligned Industrial 2-Stage Miller OTA Blueprint')
print('============================================================')
print('\nManufactured Device Sizes:')
print(f'  M1/2  W = {w_12_round} µm,  L = {l_12} µm   [NMOS Input Pair]')
print(f'  M3/4  W = {w_34_round} µm,  L = {l_34} µm   [PMOS Active Load]')
print(f'  M5    W = {w_5_round} µm,  L = {l_5} µm   [PMOS CS Driver]')
print(f'  M6    W = {w_6_round} µm,  L = {l_678} µm   [NMOS Ref Mirror]')
print(f'  M7    W = {w_7_round} µm,  L = {l_678} µm   [NMOS Tail Mirror]')
print(f'  M8    W = {w_8_round} µm,  L = {l_678} µm   [NMOS Load Mirror]')

print('\nPassives Allocation:')
print(f'  Miller Cap Cm       = {c_m/1e-15:.1f} fF ({w_cm_approx:.1f} x {w_cm_approx:.1f} µm MOM cap)')
print(f'  Nulling Resistor Rz = {rz/1e3:.2f} kΩ')

print('\nEvaluated Performance Metrics:')
print(f'  Target Specification UGBW = {f_bw/1e6:.2f} MHz')
print(f'  Actual Predicted UGBW     = {f_bw_actual/1e6:.2f} MHz (including layout parasitics)')
print(f'  Stage 1 Open-Loop Gain A1 = {20*np.log10(a0):.1f} dB')
print(f'  Stage 2 Open-Loop Gain A2 = {20*np.log10(a1):.1f} dB')
print(f'  Total Open-Loop Gain A0   = {20*np.log10(a0_total):.1f} dB (Specification: >= {gain} dB)')

print('\nCurrent Consumption Budget:')
print(f'  Stage 1 Tail Branch (I_tail) = {i_tail/1e-6:.2f} µA')
print(f'  Stage 2 Core Branch (Id_M5)  = {id_m5/1e-6:.2f} µA')
print(f'  Total Net Core Current Draw  = {(i_tail + id_m5)/1e-6:.2f} µA (Limit Budget: <= {i_total_limit/1e-6:.2f} µA)')
print(f"  Power Dissipation (at VDD = {vdd_max}V) = {power_dissipation_max*1e6:.2f} µW")
print('============================================================')
--- Node Parasitic Extraction & Actual UGBW ---
slewing time = 0.046 µs
settling time = 0.032 µs
Internal Node Parasitic Load = 99.54 fF and of which m5 80.75863693703191
Effective Miller Capacitance = 599.54 fF
Total Current from VDD Rail = 47.00 µA

============================================================
  Textbook-Aligned Industrial 2-Stage Miller OTA Blueprint
============================================================

Manufactured Device Sizes:
  M1/2  W = 2.5 µm,  L = 2 µm   [NMOS Input Pair]
  M3/4  W = 3.5 µm,  L = 2 µm   [PMOS Active Load]
  M5    W = 9.5 µm,  L = 2 µm   [PMOS CS Driver]
  M6    W = 2.0 µm,  L = 2 µm   [NMOS Ref Mirror]
  M7    W = 1.5 µm,  L = 2 µm   [NMOS Tail Mirror]
  M8    W = 1.0 µm,  L = 2 µm   [NMOS Load Mirror]

Passives Allocation:
  Miller Cap Cm       = 500.0 fF (18.3 x 18.3 µm MOM cap)
  Nulling Resistor Rz = 20.00 kΩ

Evaluated Performance Metrics:
  Target Specification UGBW = 10.00 MHz
  Actual Predicted UGBW     = 24.82 MHz (including layout parasitics)
  Stage 1 Open-Loop Gain A1 = 31.6 dB
  Stage 2 Open-Loop Gain A2 = 34.3 dB
  Total Open-Loop Gain A0   = 65.9 dB (Specification: >= 60 dB)

Current Consumption Budget:
  Stage 1 Tail Branch (I_tail) = 17.00 µA
  Stage 2 Core Branch (Id_M5)  = 10.00 µA
  Total Net Core Current Draw  = 27.00 µA (Limit Budget: <= 30.00 µA)
  Power Dissipation (at VDD = 1.55V) = 72.85 µW
============================================================